/*
 *    Copyright 2014-2017 Alexey Danilov
 *    Copyright 2021 Institute of Software Chinese Academy of Sciences, ISRC

 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.danikula.videocache.file;

import com.danikula.videocache.util.LogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.danikula.videocache.Constants.TEMP_POSTFIX;

/**
 * {@link DiskUsage} that uses LRU (Least Recently Used) strategy to trim cache.
 *
 * @author Alexey Danilov (danikula@gmail.com).
 */
public abstract class LruDiskUsage implements DiskUsage {
    private static final String TAG = "LruDiskUsage";
    private final ExecutorService workerThread = Executors.newSingleThreadExecutor();
    private final ExecutorService removeThread = Executors.newSingleThreadExecutor();

    @Override
    public void touch(File file) throws IOException {
        workerThread.submit(new TouchCallable(file));
    }

    @Override
    public void removeTemp(File file) throws IOException {
        removeThread.submit(new RemoveCallable(file));
    }

    private void touchInBackground(File file) throws IOException {
        Files.setLastModifiedNow(file);
        List<File> files = Files.getLruListFiles(file.getParentFile());
        trim(files);
    }

    private void removeInBackground(File file) {
        if (file == null || file.getName().contains(TEMP_POSTFIX)) {
            return;
        }
        File dirctoryFile = file.getParentFile();
        if (dirctoryFile == null || !dirctoryFile.isDirectory() || !dirctoryFile.exists()) {
            return;
        }
        File parentFile = dirctoryFile.getParentFile();
        if (parentFile == null || !parentFile.isDirectory() || !parentFile.exists()) {
            return;
        }
        String name = file.getName();
        String trueName = name.substring(0, name.contains(".") ? name.lastIndexOf(".") : name.length());
        File[] fileList = parentFile.listFiles();
        if (fileList == null || fileList.length == 0) {
            return;
        }
        for (int i = 0; i < fileList.length; i++) {
            File file1 = fileList[i];
            if (file1.isDirectory() && file1.getName().equals(trueName)) {
                deleteFiles(file1);
                break;
            }
        }
    }

    private void deleteFiles(File parentFile) {
        File[] files = parentFile.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            if (file.isDirectory()) {
                deleteFiles(file);
            } else {
                boolean delete = file.delete();
                LogUtil.info(TAG, "file delete is success :" + delete);
            }
        }
        boolean delete = parentFile.delete();
        LogUtil.info(TAG, "parentFile delete is success :" + delete);
    }

    /**
     * 缓存文件的大小/个数是否满足要求
     *
     * @param file       当前缓存文件
     * @param totalSize  缓存文件夹的总大小
     * @param totalCount 缓存文件夹的文件总个数
     * @return 是否满足要求
     */
    protected abstract boolean accept(File file, long totalSize, int totalCount);

    private void trim(List<File> files) {
        long totalSize = countTotalSize(files);
        int totalCount = files.size();
        for (File file : files) {
            boolean accepted = accept(file, totalSize, totalCount);
            if (!accepted) {
                long fileSize = file.length();
                boolean deleted = file.delete();
                if (deleted) {
                    totalCount--;
                    totalSize -= fileSize;
                    LogUtil.info(TAG, "Cache file:{} is deleted because it exceeds cache limit" + file);
                } else {
                    LogUtil.error(TAG, "Error deleting file {} for trimming cache" + file);
                }
            }
        }
    }

    private long countTotalSize(List<File> files) {
        long totalSize = 0;
        for (File file : files) {
            totalSize += file.length();
        }
        return totalSize;
    }

    private class TouchCallable implements Callable<Optional<Void>> {
        private final File file;

        TouchCallable(File file) {
            this.file = file;
        }

        @Override
        public Optional<Void> call() throws Exception {
            touchInBackground(file);
            return Optional.empty();
        }
    }

    private class RemoveCallable implements Callable<Optional<Void>> {
        private final File file;

        RemoveCallable(File file) {
            this.file = file;
        }

        @Override
        public Optional<Void> call() throws Exception {
            removeInBackground(file);
            return Optional.empty();
        }
    }
}
